package com.scd.boot.mvc.interceptor;

import cn.hutool.crypto.digest.DigestUtil;
import com.scd.boot.anno.CheckSign;
import com.scd.boot.commonconst.CheckSignConst;
import com.scd.boot.config.properties.SignConfig;
import com.scd.boot.exception.CheckSignException;
import com.scd.boot.exception.ParamException;
import com.scd.boot.request.ServletRequestCopyWrapper;
import com.scd.boot.util.HttpUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;

/**
 * appId=appId&appKey=appKey&timeStamp=当前时间戳&body=json参数
 * appId=appId&appKey=appKey&timeStamp=当前时间戳&key1=value1&key2=value2,key 按照升序排列
 */
@Component
@Slf4j
public class CheckSignInterceptor implements HandlerInterceptor {

    private SignConfig signConfig;

    public CheckSignInterceptor(SignConfig signConfig) {
        this.signConfig = signConfig;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            CheckSign checkSign = handlerMethod.getMethodAnnotation(CheckSign.class);
            if (checkSign == null) {
                log.info("{} check sign not config", request.getRequestURI());
                return true;
            }
            // appId, sign, timestamp
            String appId = request.getHeader("appId");
            String sign = request.getHeader("sign");
            String timeStamp = request.getHeader("timestamp");
            checkParam(appId, sign, timeStamp);
            // 校验请求时间
            checkTime(timeStamp, checkSign);
            String contentType = request.getContentType();
            StringBuilder originParam = new StringBuilder();
            String appKey = signConfig.getSystem().get(appId);
            originParam.append("appId=").append(appId).append("&appKey=").append(appKey)
                    .append("&timeStamp=").append(timeStamp);
            if (StringUtils.isNotBlank(contentType)
                    && MediaType.APPLICATION_JSON.includes(MediaType.parseMediaType(contentType))) {
                ServletRequestCopyWrapper servletRequestCopyWrapper;
                if (request instanceof ServletRequestCopyWrapper) {
                    servletRequestCopyWrapper = (ServletRequestCopyWrapper) request;
                } else {
                    servletRequestCopyWrapper = new ServletRequestCopyWrapper(request);
                }
                log.info("request origin data {}", servletRequestCopyWrapper.getBodyParamStr());
                String encodeBody = HttpUtil.encodeStr(servletRequestCopyWrapper.getBodyParamStr());
                originParam.append("&body=").append(encodeBody);
            } else {
                Enumeration<String> enumeration = request.getParameterNames();
                List<String> paramList = new ArrayList<>();
                while (enumeration.hasMoreElements()) {
                    paramList.add(enumeration.nextElement());
                }
                paramList.sort(String::compareTo);
                paramList.forEach(key -> {
                    originParam.append("&").append(key).append("=")
                            .append(HttpUtil.encodeStr(request.getParameter(key)));
                });
            }
            String validSign = DigestUtil.sha512Hex(originParam.toString(), StandardCharsets.UTF_8.name());
            if (!validSign.equals(sign)) {
                log.info("request sign {} valid sign {}", sign, validSign);
                throw new CheckSignException("签名错误");
            }
        }
        return true;
    }

    private void checkTime(String timeStamp, CheckSign checkSign) {
        if (checkSign.timeInterval() > CheckSignConst.INT_ZERO) {
            long curTime = System.currentTimeMillis();
            long requestTimeStamp = Long.parseLong(timeStamp);
            long interval = checkSign.timeInterval() * 1000L;
            long calInterval = Math.abs(requestTimeStamp - curTime);
            if (calInterval > interval) {
                log.info("请求时间错误 当前时间 {} 请求时间 {} 时间间隔 {} ms", curTime, requestTimeStamp, calInterval);
                throw new ParamException("参数错误");
            }
        }
    }

    private void checkParam(String appId, String sign, String timeStamp) {
        // appId, sign, timestamp
        if (StringUtils.isBlank(appId)) {
            log.info("请求头appId 不能为空");
            throw new ParamException("参数错误");
        }
        if (!signConfig.getSystem().containsKey(appId)) {
            log.info("请求appId错误 {}", appId);
            throw new ParamException("参数错误");
        }
        if (StringUtils.isBlank(sign)) {
            log.info("请求头sign 不能为空");
            throw new ParamException("参数错误");
        }
        if (StringUtils.isBlank(timeStamp)) {
            log.info("请求头timestamp 不能为空");
            throw new ParamException("参数错误");
        }
    }
}
